package com.monkey.service;

import com.google.common.cache.*;
import com.monkey.core.ErrorCode;
import com.monkey.core.WebRet;
import com.monkey.generator.dao.*;
import com.monkey.generator.model.*;
import com.monkey.model.monkey.Monkey;
import com.monkey.model.user.*;
import com.monkey.util.DateUtil;
import com.monkey.util.IDGenerator;
import com.monkey.util.WebConfig;
import com.monkey.util.sms.SMSTXUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.monkey.service.MonkeyService.MONKEY_STATUS_NORMAL;

/**
 * desc:
 * auth: chenxiaobin
 * data: 2017/4/1.
 */
@Service
@Transactional
public class UserService {
    private static Logger logger = LoggerFactory.getLogger(UserService.class);
    public static final int ORDER_CREATE = 0;   // 订单创建
    public static final int ORDER_COMPLETE = 1; // 订单完成
    public static final int ORDER_CANCEL = 2;   // 订单取消
    public static final int ORDER_HISTORY_SELLIN = 1;   // 买入
    public static final int ORDER_HISTORY_SELLOUT = 2;  // 卖出
    public static final int ORDER_HISTORY_MARRY = 3;    // 生育
    public static final int ACTION_FEED = 1;            // 喂食
    public static final int ACTION_MARKET = 2;          // 市场交易费
    public static final int ACTION_MARRY = 3;           // 鹊桥交易费
    public static final int ACTION_ADMIN = 4;           // 后台充值
    public static final int ACTION_BONUS = 5;           // 掘金
    public static final int ACTION_CHARGE = 6;          // 充值
    public static final int ACTION_MARKET_SELL = 7;     // 市场挂单手续费
    public static final int ACTION_MARRY_SELL = 8;      // 鹊桥挂单手续费
    public static final int ACTION_DRAW = 9;            // 提现
    public static final int ACTION_LUCK = 10;           // 抽奖
    public static final int ACTION_SYNTHESISE = 11;     // 宠物合成
    private LoadingCache<Integer, User> userCache;              // 用户缓存
    private Map<String, Integer> idMap = new HashMap<>();       // 手机号关联用户id(手机号->用户id)
    private Map<String, UserToken> tokenMap = new HashMap<>();  // token(token->token信息)
    private Map<String, UserCode> codeMap = new HashMap<>();    // 用户验证码缓存(手机号->验证码)
    private int userIndex;

    @Resource
    private MonkeyService monkeyService = null;

    @Resource
    private TUserMapper userMapper = null;

    @Resource
    private TUserActionMapper userActionMapper = null;

    @Resource
    private TUserBillMapper userBillMapper = null;

    @Resource
    private TUserGoldMapper userGoldMapper = null;

    @Resource
    private TUserMonkeyMapper userMonkeyMapper = null;

    @Resource
    private IDGenerator idGenerator = null;

    @Resource
    private DateUtil dateUtil = null;

    @Resource
    private SMSTXUtil smsTXUtil = null;

    @Resource
    private WebConfig webConfig = null;

    public void init() {
        initUserIndex();
        initCache();
    }

    public WebRet check(String phone) {
        User user = getUser(phone);
        if (null == user) {
            return new WebRet(ErrorCode.USER_NO_EXIST);
        }
        return new WebRet(ErrorCode.SUCCESS);
    }

    public UserLoginData login(String phone, String password) {
        User user = getUser(phone);
        if (null == user) {
            return new UserLoginData(ErrorCode.USER_NO_EXIST);
        }
        if (!password.equals(user.getPassword())) {
            return new UserLoginData(ErrorCode.PASSWORD_ERROR);
        }

        TUser user1 = new TUser();
        user1.setLastlogintime(new Date());
        user1.setLogincount(user.getLogincount() + 1);
        TUserExample example = new TUserExample();
        example.or().andUseridEqualTo(user.getId());
        example.setLimit(1);
        userMapper.updateByExampleSelective(user1, example);

        user.setLogincount(user1.getLogincount());
        UserData data = new UserData(user);

        // 生成token
        String token = idGenerator.randomUUID();
        insertToken(phone, user.getId(), token);
        return new UserLoginData(ErrorCode.SUCCESS, token, data);
    }

    public User getUserWithToken(String token) {
        // 验证token
        UserToken t;
        synchronized (this) {
            t = tokenMap.get(token);
        }
        if (null == t) {
            return null;
        }

        User user = getUser(t.getPhone());
        if (null == user) {
            return null;
        }
        return user;
    }

    public UserData getInfo(String token) {
        User user = getUserWithToken(token);
        if (null == user) {
            return null;
        }
        return new UserData(user);
    }

    public void insertToken(String phone, int userId, String token) {
        int timeout = webConfig.getInt("user.token.timeout");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.HOUR_OF_DAY, timeout);
        UserToken t = new UserToken(token, userId, phone, calendar.getTime());
        synchronized (this) {
            tokenMap.put(token, t);
        }
    }

    public void deleteToken(String token) {
        synchronized (this) {
            tokenMap.remove(token);
        }
    }

    // 获取验证码，注册/忘记密码
    public UserCode getCode(String phone) {
        // 如果已经有数据，一分钟内不重复发送
        Date now = new Date();
        Calendar calendar = Calendar.getInstance();

        UserCode info;
        synchronized (this) {
            info = codeMap.get(phone);
        }
        if (null != info) {
            calendar.setTime(now);
            calendar.add(Calendar.MINUTE, 1);
            if (now.after(calendar.getTime())) {
                return new UserCode(ErrorCode.MODIFY_TIME_ERROR);
            }
        }

        // 发短信
        int timeout = webConfig.getInt("user.code.timeout");
        calendar.setTime(now);
        calendar.add(Calendar.MINUTE, timeout);

        int code = idGenerator.randomId();
        if (!smsTXUtil.send(phone, code, timeout)) {
            return new UserCode(ErrorCode.SMS_ERROR);
        }

        UserCode ret = new UserCode(ErrorCode.SUCCESS, calendar.getTime(), code);
        synchronized (this) {
            codeMap.put(phone, ret);
        }
        return ret;
    }

    public UserRegisterData register(String phone, String password, int code) {
        Date now = new Date();

        // 校验code
        UserCode info;
        synchronized (this) {
            info = codeMap.get(phone);
        }

        if (null == info || info.getVerify() != code) {
            return new UserRegisterData(ErrorCode.MODIFY_CODE_ERROR);
        }
        if (now.after(info.getTime())) {
            return new UserRegisterData(ErrorCode.MODIFY_CODE_TIMEOUT);
        }

        User user = getUser(phone);
        if (null != user) {
            return new UserRegisterData(ErrorCode.USER_HAS_EXIST);
        }

        int userId;
        synchronized (this) {
            userId = ++userIndex;
        }
        // 插入数据
        BigDecimal dec = new BigDecimal(0);
        TUser tUser = new TUser();
        tUser.setUserid(userId);
        tUser.setName(createName());
        tUser.setPhone(phone);
        tUser.setPassword(password);
        tUser.setHeadid(0);
        tUser.setHead("");
        tUser.setWallet("");
        tUser.setPaypwd(0);
        tUser.setMoney(dec);
        tUser.setGold(dec);
        tUser.setBonustotal(dec);
        tUser.setPlatform(0);
        tUser.setDevice("");
        tUser.setRegistertime(now);
        tUser.setLastlogintime(now);
        tUser.setLastloginip("");
        tUser.setLogincount(0);
        tUser.setEnable(true);
        tUser.setLucky(0);
        userMapper.insert(tUser);

        user = new User(tUser);
        if (!initNewUser(user)) {
            logger.error("init new user error:{}", phone);
            return new UserRegisterData(ErrorCode.INIT_NEW_ERROR);
        }
        synchronized (this) {
            idMap.put(user.getPhone(), user.getId());
        }
        return new UserRegisterData(true, ErrorCode.SUCCESS, userId);
    }

    public WebRet modifyPwdWithCode(String phone, String newPwd, int code) {
        // 校验code
        UserCode info;
        synchronized (this) {
            info = codeMap.get(phone);
        }
        if (null == info || info.getVerify() != code) {
            return new WebRet(ErrorCode.MODIFY_CODE_ERROR);
        }
        Date now = new Date();
        if (now.after(info.getTime())) {
            return new WebRet(ErrorCode.MODIFY_CODE_TIMEOUT);
        }

        User user = getUser(phone);
        if (null == user) {
            logger.error("modify pwd error:{}", phone);
            return new WebRet(ErrorCode.USER_NO_EXIST);
        }

        TUser newUser = new TUser();
        newUser.setPassword(newPwd);

        TUserExample example = new TUserExample();
        example.or().andUseridEqualTo(user.getId());
        example.setLimit(1);
        if (0 == userMapper.updateByExampleSelective(newUser, example)) {
            return new WebRet(ErrorCode.WRITE_DB_ERROR);
        }

        user.setPassword(newPwd);
        return new WebRet(ErrorCode.SUCCESS);
    }

    public WebRet modifyPwd(String phone, String oldPwd, String newPwd) {
        User user = getUser(phone);
        if (null == user) {
            logger.error("modify pwd error:{}", phone);
            return new WebRet(ErrorCode.USER_NO_EXIST);
        }
        if (!user.getPassword().equals(oldPwd)) {
            return new WebRet(ErrorCode.PWD_NO_EQUAL);
        }

        TUser newUser = new TUser();
        newUser.setPassword(newPwd);

        TUserExample example = new TUserExample();
        example.or().andUseridEqualTo(user.getId());
        example.setLimit(1);
        if (0 == userMapper.updateByExampleSelective(newUser, example)) {
            return new WebRet(ErrorCode.WRITE_DB_ERROR);
        }

        user.setPassword(newPwd);
        return new WebRet(ErrorCode.SUCCESS);
    }

    public void updateUserHead(User user, Monkey monkey) {
        int userId = user.getId();
        int monkeyId = monkey.getId();
        if (0 == user.getHeadId()) {
            user.setHeadId(monkeyId);
            user.setHead(monkey.getUuid());
        } else {
            Monkey monkey2 = monkeyService.getMonkey(user.getHeadId());
            if (monkey2.getWeight() < 0.0001 && monkey2.getGeneration() < monkey.getGeneration()) {
                user.setHeadId(monkeyId);
                user.setHead(monkey.getUuid());
            } else {
                return;
            }
        }

        TUser user1 = new TUser();
        user1.setHeadid(monkeyId);
        user1.setHead(user.getHead());
        TUserExample example = new TUserExample();
        example.or().andUseridEqualTo(userId);
        example.setLimit(1);
        if (0 == userMapper.updateByExampleSelective(user1, example)) {
            logger.info("user set head error:{}, {}", userId, user.getHead());
        }
    }

    public WebRet bindWallet(User user, String wallet, int code) {
        if (42 != wallet.length()) {
            return new WebRet(ErrorCode.WALLET_SIZE_ERROR);
        }

        if (null != user.getWallet()) {
            // 校验code
            UserCode info;
            synchronized (this) {
                info = codeMap.get(user.getPhone());
            }
            if (null == info || info.getVerify() != code) {
                return new WebRet(ErrorCode.MODIFY_CODE_ERROR);
            }
            Date now = new Date();
            if (now.after(info.getTime())) {
                return new WebRet(ErrorCode.MODIFY_CODE_TIMEOUT);
            }
        }

        // 钱包必须唯一
        TUserExample example = new TUserExample();
        example.or().andWalletEqualTo(wallet);
        example.setLimit(1);
        List<TUser> list = userMapper.selectByExample(example);
        if (null != list && !list.isEmpty()) {
            return new WebRet(ErrorCode.WALLET_HAS_EXIST);
        }

        // 更新钱包
        int userId = user.getId();
        TUser newUser = new TUser();
        newUser.setWallet(wallet);

        example = new TUserExample();
        example.or().andUseridEqualTo(userId);
        example.setLimit(1);
        if (0 == userMapper.updateByExampleSelective(newUser, example)) {
            logger.info("bind wallet error:{}, {}", userId, wallet);
            return new WebRet(ErrorCode.WRITE_DB_ERROR);
        }

        user.setWallet(newUser.getWallet());
        return new WebRet(ErrorCode.SUCCESS);
    }

    public WebRet modifyName(User user, String name) {
        TUser user1 = new TUser();
        user1.setName(name);
        TUserExample example = new TUserExample();
        example.or().andUseridEqualTo(user.getId());
        example.setLimit(1);
        if (0 == userMapper.updateByExampleSelective(user1, example)) {
            return new WebRet(ErrorCode.WRITE_DB_ERROR);
        }
        user.setName(name);
        return new WebRet(ErrorCode.SUCCESS);
    }

    public List<UserBill> getBillList(User user, int status, int offset, int num) {
        List<UserBill> ret = new ArrayList<>();
        TUserBillExample example = new TUserBillExample();
        if (0 == status) {
            example.or().andUseridEqualTo(user.getId());
        } else {
            example.or().andUseridEqualTo(user.getId()).andActionEqualTo(status);
        }
        example.setOffset(offset);
        example.setLimit(num);
        example.setOrderByClause("createTime DESC");
        List<TUserBill> bills = userBillMapper.selectByExample(example);
        if (null == bills || bills.isEmpty()) {
            return ret;
        }
        SimpleDateFormat sdf = dateUtil.getDateFormat();
        for (TUserBill bill : bills) {
            ret.add(new UserBill(bill, sdf));
        }
        return ret;
    }

    /**
     * 仅供后台修改
     */
    public WebRet modifyGold(String phone, float value, int action) {
        User user = getUser(phone);
        if (null != user) {
            BigDecimal origin = user.getGold();
            user.setGold(new BigDecimal(origin.floatValue() + value));

            TUser user1 = new TUser();
            user1.setGold(user.getGold());
            TUserExample example = new TUserExample();
            example.or().andUseridEqualTo(user.getId());
            example.setLimit(1);
            userMapper.updateByExampleSelective(user1, example);

            TUserGold gold = new TUserGold();
            gold.setUserid(user.getId());
            gold.setOrigin(origin);
            gold.setValue(new BigDecimal(value));
            gold.setData1(0);
            gold.setData2(0);
            gold.setData3(0);
            gold.setAction(action);
            gold.setCreatetime(new Date());
            userGoldMapper.insert(gold);
        } else {
            logger.info("modify gold with phone error:{}, {}", phone, value);
            return new WebRet(ErrorCode.USER_NO_EXIST);
        }
        return new WebRet(ErrorCode.SUCCESS);
    }

    /**
     * 更新玩家余额
     *
     * @param status 是行为：喂食，交易，交配。
     * @param action 是动作：挂单，撤单等。
     */
    public int updateUserMoney(User user, int monkeyId, float money, float water, int status, int action) {
        int userId = user.getId();
        BigDecimal origin = user.getMoney();
        BigDecimal moneyD = new BigDecimal(money);
        user.setMoney(origin.add(moneyD));

        // 保存玩家数据
        TUser user1 = new TUser();
        user1.setMoney(user.getMoney());
        TUserExample example = new TUserExample();
        example.or().andUseridEqualTo(userId);
        example.setLimit(1);
        int ret = userMapper.updateByExampleSelective(user1, example);

        // 记录玩家动作
        Date now = new Date();
        if (MONKEY_STATUS_NORMAL != status) {
            TUserAction action1 = new TUserAction();
            action1.setUserid(userId);
            action1.setAction(status);
            action1.setMoney(moneyD);
            action1.setWater(new BigDecimal(water));
            action1.setData1(monkeyId);
            action1.setData2(0);
            action1.setData3(0);
            action1.setCreatetime(now);
            if (0 == userActionMapper.insert(action1)) {
                logger.info("insert user action error:{}, {}, {}, {}, {}", userId, monkeyId, money, water, action);
            }
        }

        // 记录收支明细
        TUserBill bill = new TUserBill();
        bill.setUserid(userId);
        bill.setOrigin(origin);
        bill.setValue(moneyD);
        bill.setAction(action);
        bill.setData1(monkeyId);
        bill.setData2(0);
        bill.setData3(0);
        bill.setCreatetime(now);
        if (0 == userBillMapper.insert(bill)) {
            logger.info("insert user bill error:{}, {}, {}, {}", userId, origin, money, action);
        }

        if (0 == ret) {
            logger.info("update user money action error:{}, {}, {}, {}, {}", userId, monkeyId, money, water, action);
            return ErrorCode.WRITE_DB_ERROR;
        }
        return ErrorCode.SUCCESS;
    }

    /**
     * 更新玩家金币
     *
     * @param status 是行为：喂食，交易，交配。
     * @param action 是动作：挂单，撤单等。
     */
    public int updateUserGold(User user, float value, int action) {
        int userId = user.getId();
        BigDecimal origin = user.getGold();
        BigDecimal goldD = new BigDecimal(value);
        user.setGold(origin.add(goldD));

        // 保存玩家数据
        TUser user1 = new TUser();
        user1.setGold(user.getGold());
        TUserExample example = new TUserExample();
        example.or().andUseridEqualTo(userId);
        example.setLimit(1);
        int ret = userMapper.updateByExampleSelective(user1, example);

        // 记录收支明细
        TUserGold gold = new TUserGold();
        gold.setUserid(user.getId());
        gold.setOrigin(origin);
        gold.setValue(new BigDecimal(value));
        gold.setData1(0);
        gold.setData2(0);
        gold.setData3(0);
        gold.setAction(action);
        gold.setCreatetime(new Date());
        if (0 == userGoldMapper.insert(gold)) {
            logger.info("insert user gold error:{}, {}, {}, {}", userId, origin, value, action);
        }

        if (0 == ret) {
            logger.info("update user gold action error:{}, {}, {}, {}", userId, value, action);
            return ErrorCode.WRITE_DB_ERROR;
        }
        return ErrorCode.SUCCESS;
    }

    public User getUser(int id) {
        try {
            return userCache.get(id);
        } catch (Exception e) {
            return null;
        }
    }

    public User getUser(String phone) {
        Integer id;
        synchronized (this) {
            id = idMap.get(phone);
        }
        if (null != id) {
            return getUser(id);
        }

        // 从数据库获取
        TUserExample example = new TUserExample();
        example.or().andPhoneEqualTo(phone);
        example.setLimit(1);
        List<TUser> users = userMapper.selectByExample(example);
        if (null == users || users.isEmpty()) {
            return null;
        }

        User user = new User(users.get(0));
        int userId = user.getId();
        synchronized (this) {
            idMap.put(phone, userId);
        }
        addUserCache(userId, user);
        return user;
    }

    public User getUserByWallet(String wallet) {
        // 从数据库获取
        TUserExample example = new TUserExample();
        example.or().andWalletEqualTo(wallet);
        example.setLimit(1);
        List<TUser> users = userMapper.selectByExample(example);
        if (null == users || users.isEmpty()) {
            return null;
        }

        User user = new User(users.get(0));
        int userId = user.getId();
        addUserCache(userId, user);
        return user;
    }

    public String getSystemWallet() {
        return webConfig.get("wkc.wallet");
    }

    private void addUserCache(Integer id, User user) {
        userCache.put(id, user);
    }

    private boolean initNewUser(User user) {
        return monkeyService.initNewUser(user);
    }

    private void initUserIndex() {
        // 获取最大id
        userIndex = webConfig.getInt("user.id.start");

        TUserExample example = new TUserExample();
        example.setOrderByClause("userId desc");
        example.setLimit(1);

        List<TUser> users = userMapper.selectByExample(example);
        if (null != users && !users.isEmpty()) {
            int id = users.get(0).getUserid();
            if (id > userIndex) {
                userIndex = id;
            }
        }
        logger.info("set user index:{}", userIndex);
    }

    private String createName() {
        String name = webConfig.get("user.name.pre");
        name += idGenerator.randomId();
        return name;
    }

    private void initCache() {
        userCache = CacheBuilder.newBuilder()
                .recordStats()
                .maximumSize(webConfig.getInt("user.cache.maxSize"))
                .expireAfterAccess(webConfig.getInt("user.cache.expireAfterAccess"), TimeUnit.HOURS)
                .expireAfterWrite(webConfig.getInt("user.cache.expireAfterWrite"), TimeUnit.MINUTES)
                .removalListener(new RemovalListener<Integer, User>() {
                    @Override
                    public void onRemoval(RemovalNotification<Integer, User> notification) {
                        User user = notification.getValue();
                        if (null != user) {
                            logger.info("user has removed. uid:{}", notification.getKey());
                        }
                    }
                })
                .build(new CacheLoader<Integer, User>() {
                    @Override
                    public User load(Integer id) {
                        try {
                            logger.info("load user from db:{}", id);
                            TUserExample example = new TUserExample();
                            example.or().andUseridEqualTo(id);
                            example.setLimit(1);
                            List<TUser> users = userMapper.selectByExample(example);
                            if (null == users || users.isEmpty()) {
                                logger.info("load user null");
                                return null;
                            }

                            // 统计宠物数量
                            TUserMonkeyExample example1 = new TUserMonkeyExample();
                            example1.or().andUseridEqualTo(id);
                            int num = (int) userMonkeyMapper.countByExample(example1);

                            User user = new User(users.get(0));
                            user.setMonkeyNum(num);
                            synchronized (this) {
                                idMap.put(user.getPhone(), user.getId());
                            }
                            return user;
                        } catch (Exception e) {
                            logger.error("load user error:{}", e);
                            return null;
                        }
                    }
                });
    }
    
    // 更新玩家宠物合成幸运值
    public int updateUserLucky(User user, Integer lucky, int action) {
    	int userId = user.getId();
        Integer origin = user.getLucky();
        user.setLucky(lucky);

        // 保存玩家数据
        TUser user1 = new TUser();
        user1.setLucky(user.getLucky());
        TUserExample example = new TUserExample();
        example.or().andUseridEqualTo(userId);
        example.setLimit(1);
        int ret = userMapper.updateByExampleSelective(user1, example);
        
        if (0 == ret) {
            logger.info("update user lucky action error:{}, {}, {}, {}", userId, origin, lucky, action);
            return ErrorCode.WRITE_DB_ERROR;
        }
        
        return ErrorCode.SUCCESS;
    }
    
    // 扣除玩家金币
    public int deductUserGold(User user, float value, int action) {
    	int userId = user.getId();
    	if(value < 0){
    		logger.info("deduct user gold action error:{}, {}, {}, {}", userId, value, action);
    		return ErrorCode.WRITE_DB_ERROR;
    	}
        BigDecimal origin = user.getGold();
        BigDecimal goldD = new BigDecimal(value);
        user.setGold(origin.subtract(goldD));

        // 保存玩家数据
        TUser user1 = new TUser();
        user1.setGold(user.getGold());
        TUserExample example = new TUserExample();
        example.or().andUseridEqualTo(userId);
        example.setLimit(1);
        int ret = userMapper.updateByExampleSelective(user1, example);

        if (0 == ret) {
            logger.info("deduct user gold action error:{}, {}, {}, {}", userId, value, action);
            return ErrorCode.WRITE_DB_ERROR;
        }
        return ErrorCode.SUCCESS;
    }
}
